import re
from chord import *

CHORDS = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
MAJOR_ORDER = (0, 2, 4, 5, 7, 9, 11, 12)
GAMMA_SIZE = 12

#TODO add invalid chords statement
#TODO be able to identify m# or#m sequences

class DiezFilter:
    def match(self, chordString):
        return re.search ("#|b", chordString) != None
    
    def apply(self, chordString, chordNotes):
        #todo - use method reference
        up = re.search ("#", chordString) != None
        newChordNotes = list()
        for note in chordNotes:
            if up:
                newChordNotes.append(ChordsParser.upHalfTone(note))
            else:
                newChordNotes.append(ChordsParser.downHalfTone(note))
        #print "DiezFilter=> " +  " ".join(newChordNotes)
        return newChordNotes
   
class MinorFilter:
    def match(self, chordString):
        return re.search ("m(?![aj])", chordString) != None
    
    def apply(self, chordString, chord):
        chord[1] = ChordsParser.downHalfTone(chord[1])
        #print "MinorFilter=> " + " ".join(chord)
        return chord

class SeptFilter:
    def match(self, chordString):
        return re.search("7", chordString) != None
    
    def apply(self, chordString, chord):
        if (len(chord) != 3):
            raise Exception ("SeptFilter accepts only 3-note chords")
        lastNote = chord [2]
        fourthNote = ChordsParser.upToneAndHalf(lastNote)
        chord.append (fourthNote)
        #print "SeptFilter=> " + " ".join(chord)
        return chord

class MajorFilter:
    def match(self, chordString):
        return re.search ("M|maj", chordString) != None;
    
    def apply(self, chordstring, chord):
        if (len(chord) != 4):
            return chord
        
        lastNote = chord [3]
        fourthNote = ChordsParser.upHalfTone(lastNote)
        chord = chord [0:3]
        chord.append (fourthNote)
        #print "MajorFilter=> " + " ".join(chord)
        return chord

    
#rename to ChordParser
class ChordsParser:
    
    def __init__ (self, chordString):
        self.filters = list()
        self.filters.append(SeptFilter());
        self.filters.append(DiezFilter());
        self.filters.append(MinorFilter());
        self.filters.append(MajorFilter());

        #not really fair, #\b are not taken, but fixed with the filter
        #TODO change to use whole CHORDS list
        chord = chordString [0];
        if CHORDS.index (chord) == -1:
            raise Exception ()
        
        self.keyShift = CHORDS.index (chord)
        self.chordString = chordString
        #print "key is %s, shift is %d" % (chord, self.keyShift)  

    def getNotes (self):
        step1 = CHORDS [self.keyShift]    
        step2 = self.toStep (3)
        step3 = self.toStep (5)
    
        baseChord = [step1, step2, step3]
        #print "Base chord=> "  + " ".join(baseChord)
        return self.process (baseChord)
    
    def getChord (self):
        #TODO this is hack, keep chord as internal representation
        notes = self.getNotes ()
        chord = Chord()
        chord.addStep(1, notes [0])
        chord.addStep(3, notes [1])
        chord.addStep(5, notes [2])
        if (len(notes) == 4):
            chord.addStep(7, notes [3])
        return chord
    
    def process (self, baseChord):
        currentChord = baseChord
        for filter in self.filters:
            if filter.match (self.chordString):
                currentChord = filter.apply (self.chordString, currentChord)
        return currentChord
        
    def toStep (self, step):
        step = step - 1 # we want to use standard notation for chords - 1,3,5 steps 
        stepShift = MAJOR_ORDER [step] + self.keyShift
        while stepShift >= GAMMA_SIZE :
            stepShift -= GAMMA_SIZE
        #print "%d shift is %d=%s" % (step + 1, stepShift, CHORDS [stepShift])
        return CHORDS [stepShift]

    @staticmethod
    def upHalfTone(note):
        shift = CHORDS.index(note)
        shift = shift + 1
        if shift == GAMMA_SIZE:
            shift=0
        return CHORDS [shift]
    
    @staticmethod
    def downHalfTone(note):
        shift = CHORDS.index(note)
        shift = shift - 1
        if shift == -1:
            shift=GAMMA_SIZE-1
        return CHORDS [shift]

    @staticmethod
    def upTwoTone(note):
        note = ChordsParser.upHalfTone (note)
        note = ChordsParser.upHalfTone (note)
        note = ChordsParser.upHalfTone (note)
        return ChordsParser.upHalfTone (note)

    @staticmethod
    def upToneAndHalf(note):
        note = ChordsParser.upHalfTone (note)
        note = ChordsParser.upHalfTone (note)
        return ChordsParser.upHalfTone (note)


#---------------------------- Tests ------------------------


